#include "SPD3D11RenderEnvironment.h"

//---------------------------------------------------------------------------------------


//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
// SPObject
//---------------------------------------------------------------------------------------
namespace SP // SPObject
{
	SPD3DObject::SPD3DObject()
	{
		m_pDevice = SPGetD3D11Device();
		m_pImmediateContext = SPGetD3D11DeviceContext();
	}

	SPD3DObject::~SPD3DObject()
	{
	}
}



//---------------------------------------------------------------------------------------

namespace SP // SPD3D11RenderEnvironment
{
	SPD3D11RenderEnvironment* SPD3D11RenderEnvironment::GetD3D11RE()
	{
		static SPD3D11RenderEnvironment staticSPRE;
		return &staticSPRE;
	}

	SPCamera* SPGetCamera()
	{
		return SPD3D11RenderEnvironment::GetD3D11RE()->m_pCamera;
	}

	ID3D11Device* SPGetD3D11Device()
	{
		return SPD3D11RenderEnvironment::GetD3D11RE()->m_pd3dDevice;
	}

	ID3D11DeviceContext* SPGetD3D11DeviceContext()
	{
		return SPD3D11RenderEnvironment::GetD3D11RE()->m_pImmediateContext;
	}

	SPD3D11RenderEnvironment* SPGetRenderEnvironment()
	{
		return SPD3D11RenderEnvironment::GetD3D11RE();
	}

	SPD3D11RenderEnvironment::SPD3D11RenderEnvironment()
	{
		m_hWnd = NULL;
		m_driverType = D3D_DRIVER_TYPE_NULL;
		m_featureLevel = D3D_FEATURE_LEVEL_11_0;
		m_pd3dDevice = NULL;
		m_pImmediateContext = NULL;
		m_pSwapChain = NULL;
		m_pRenderTargetView = NULL;
		m_pDepthStencil = NULL;
		m_pDepthStencilView = NULL;
	}

	SPD3D11RenderEnvironment::~SPD3D11RenderEnvironment()
	{

	}

	HRESULT SPD3D11RenderEnvironment::Create( HWND hWnd, UINT width, UINT height )
	{
		HRESULT hr = S_OK;

		if ( hWnd == NULL )
			return S_FALSE;
		m_hWnd = hWnd;
		m_nWidth = width;
		m_nHeight = height;
		UINT createDeviceFlags = 0;

		D3D_DRIVER_TYPE driverTypes[] =
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_WARP,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		UINT numDriverTypes = ARRAYSIZE( driverTypes );

		D3D_FEATURE_LEVEL featureLevels[] =
		{
			D3D_FEATURE_LEVEL_11_0,
		};
		UINT numFeatureLevels = ARRAYSIZE( featureLevels );

		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory( &sd, sizeof( sd ) );
		sd.BufferCount = 2;
		sd.BufferDesc.Width = width;
		sd.BufferDesc.Height = height;
		sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.OutputWindow = m_hWnd;
		sd.SampleDesc.Count = 1;
		sd.SampleDesc.Quality = 0;
		sd.Windowed = TRUE;

		for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
		{
			m_driverType = driverTypes[driverTypeIndex];
			hr = D3D11CreateDeviceAndSwapChain( NULL, m_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
				D3D11_SDK_VERSION, &sd, &m_pSwapChain, &m_pd3dDevice, &m_featureLevel, &m_pImmediateContext );
			if ( SUCCEEDED( hr ) )
				break;
		}
		if ( FAILED( hr ) )
			return hr;

		V_RETURN( CreateViews( width, height ) );

		return S_OK;
	}

	HRESULT SPD3D11RenderEnvironment::CreateViews(UINT width, UINT height)
	{
		HRESULT hr;

		//Create a render target view
		ID3D11Texture2D* pBackBuffer = NULL;
		hr = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
		if ( FAILED( hr ) )
			return hr;

		hr = m_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView );
		pBackBuffer->Release();
		if( FAILED( hr ) )
			return hr;

		// Create depth stencil texture
		D3D11_TEXTURE2D_DESC descDepth;
		ZeroMemory( &descDepth, sizeof(descDepth) );
		descDepth.Width = width;
		descDepth.Height = height;
		descDepth.MipLevels = 1;
		descDepth.ArraySize = 1;
		descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		descDepth.SampleDesc.Count = 1;
		descDepth.SampleDesc.Quality = 0;
		descDepth.Usage = D3D11_USAGE_DEFAULT;
		descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
		descDepth.CPUAccessFlags = 0;
		descDepth.MiscFlags = 0;
		hr = m_pd3dDevice->CreateTexture2D( &descDepth, NULL, &m_pDepthStencil );
		if ( FAILED( hr ) )
			return hr;

		// Create the depth stencil view
		D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
		ZeroMemory( &descDSV, sizeof(descDSV) );
		descDSV.Format = descDepth.Format;
		descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
		descDSV.Texture2D.MipSlice = 0;
		hr = m_pd3dDevice->CreateDepthStencilView( m_pDepthStencil, &descDSV, &m_pDepthStencilView );
		if ( FAILED( hr ) )
			return hr;

		m_pImmediateContext->OMSetRenderTargets( 1, &m_pRenderTargetView, m_pDepthStencilView );

		//Setup the viewport
		D3D11_VIEWPORT vp;
		vp.Width = (FLOAT)width;
		vp.Height = (FLOAT)height;
		vp.MinDepth = 0.0f;
		vp.MaxDepth = 1.0f;
		vp.TopLeftX = 0;
		vp.TopLeftY = 0;
		m_pImmediateContext->RSSetViewports( 1, &vp );

		return S_OK;
	}

	HRESULT SPD3D11RenderEnvironment::SetWindow( UINT width, UINT height )
	{
		HRESULT hr = S_OK;

		// Release our old view
		SAFE_RELEASE( m_pRenderTargetView );
		SAFE_RELEASE( m_pDepthStencil );
		SAFE_RELEASE( m_pDepthStencilView );

		V_RETURN( m_pSwapChain->ResizeBuffers( 2, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0 ) );
		V_RETURN( CreateViews( width, height ) );

		return hr;
	}

	void SPD3D11RenderEnvironment::Clear( XMFLOAT4 vColor )
	{
		m_pImmediateContext->ClearRenderTargetView( m_pRenderTargetView, (float *)&vColor );
		m_pImmediateContext->ClearDepthStencilView( m_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0, 0 );
	}

	void SPD3D11RenderEnvironment::Present()
	{
		m_pSwapChain->Present( 0, 0 );
	}

	HRESULT SPD3D11RenderEnvironment::CompileShaderFromFile( WCHAR* szFileName, D3D_SHADER_MACRO* pDefines, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut )
	{
		HRESULT hr = S_OK;

		DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

#if defined(DEBUG) || defined(_DEBUG)
		dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif

		ID3DBlob* pErrorBlob;
		hr = D3DX11CompileFromFile(szFileName, pDefines, NULL, szEntryPoint, szShaderModel,
			dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL);
		if (FAILED(hr))
		{
			if (pErrorBlob != NULL)
				OutputDebugStringA((char*)pErrorBlob->GetBufferPointer());
			if (pErrorBlob) pErrorBlob->Release();
			return hr;
		}
		if (pErrorBlob) pErrorBlob->Release();

		return S_OK;
	}

	HRESULT SPD3D11RenderEnvironment::CompileShaderFromMemory( LPCSTR pSrcData, SIZE_T SrcDataLen, D3D_SHADER_MACRO* pDefines, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut )
	{
		HRESULT hr = S_OK;

		DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

#if defined(DEBUG) || defined(_DEBUG)
		dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif

		ID3DBlob* pErrorBlob;
		hr = D3DX11CompileFromMemory( pSrcData, SrcDataLen, "None", pDefines, NULL, szEntryPoint, szShaderModel,
			dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL);
		if (FAILED(hr))
		{
			if (pErrorBlob != NULL)
				OutputDebugStringA((char*)pErrorBlob->GetBufferPointer());
			if (pErrorBlob) pErrorBlob->Release();
			return hr;
		}
		if (pErrorBlob) pErrorBlob->Release();

		return S_OK;
	}


	HRESULT SPD3D11RenderEnvironment::CreateBuffer(UINT uElementSize, UINT uCount, VOID* pInitData, ID3D11Buffer** ppBufOut)
	{
		*ppBufOut = NULL;

		D3D11_BUFFER_DESC desc;
		ZeroMemory( &desc, sizeof(desc) );
		desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
		desc.ByteWidth = uElementSize * uCount;
		desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
		desc.StructureByteStride = uElementSize;

		if ( pInitData )
		{
			D3D11_SUBRESOURCE_DATA InitData;
			InitData.pSysMem = pInitData;
			return m_pd3dDevice->CreateBuffer( &desc, &InitData, ppBufOut );
		}
		else
		{
			return m_pd3dDevice->CreateBuffer( &desc, NULL, ppBufOut );
		}
	}

	HRESULT SPD3D11RenderEnvironment::CreateBufferSRV( ID3D11Buffer* pBuffer, ID3D11ShaderResourceView** ppSRVOut )
	{
		D3D11_BUFFER_DESC descBuf;
		ZeroMemory( &descBuf, sizeof(descBuf) );
		pBuffer->GetDesc( &descBuf );

		D3D11_SHADER_RESOURCE_VIEW_DESC desc;
		ZeroMemory( &desc, sizeof(desc) );
		desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
		desc.BufferEx.FirstElement = 0;

		if ( descBuf.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS )
		{
			// This is a Raw Buffer
			desc.Format = DXGI_FORMAT_R32_TYPELESS;
			desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
			desc.BufferEx.NumElements = descBuf.ByteWidth / 4;
		} 
		else if ( descBuf.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED )
		{
			// This is a Structured Buffer
			desc.Format = DXGI_FORMAT_UNKNOWN;
			desc.BufferEx.NumElements = descBuf.ByteWidth / descBuf.StructureByteStride;
		}
		else
		{
			return E_INVALIDARG;
		}

		return m_pd3dDevice->CreateShaderResourceView( pBuffer, &desc, ppSRVOut );
	}

	HRESULT SPD3D11RenderEnvironment::CreateBufferUAV(ID3D11Buffer* pBuffer, ID3D11UnorderedAccessView** ppUAVOut )
	{
		D3D11_BUFFER_DESC descBuf;
		ZeroMemory( &descBuf, sizeof(descBuf) );
		pBuffer->GetDesc( &descBuf );

		D3D11_UNORDERED_ACCESS_VIEW_DESC desc;
		ZeroMemory( &desc, sizeof(desc) );
		desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
		desc.Buffer.FirstElement = 0;

		if ( descBuf.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS )
		{
			// This is a Raw Buffer
			desc.Format = DXGI_FORMAT_R32_TYPELESS; // Format must be DXGI_FORMAT_R32_TYPELESS, when creating Raw Unordered Access View
			desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
			desc.Buffer.NumElements = descBuf.ByteWidth / 4; 
		} 
		else if ( descBuf.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED )
		{
			// This is a Structured Buffer
			desc.Format = DXGI_FORMAT_UNKNOWN;      // Format must be must be DXGI_FORMAT_UNKNOWN, when creating a View of a Structured Buffer
			desc.Buffer.NumElements = descBuf.ByteWidth / descBuf.StructureByteStride; 
		}
		else
		{
			return E_INVALIDARG;
		}

		return m_pd3dDevice->CreateUnorderedAccessView( pBuffer, &desc, ppUAVOut );
	}

	HRESULT SPD3D11RenderEnvironment::CreateSRVFromFile( const WCHAR* strFile, ID3D11ShaderResourceView** ppSRVout )
	{
		HRESULT hr = S_OK;

		V_RETURN( D3DX11CreateShaderResourceViewFromFile( m_pd3dDevice, strFile, NULL, NULL, ppSRVout, NULL ) );


		return hr;
	}
}

